﻿namespace FSpec.Runner

open FSpec
open FSpec.Runner.Model
open Gallio.Model.Helpers 
open Gallio.Runtime.ProgressMonitoring 
open Gallio.Model
open Gallio.Model.Tree
open Gallio.Model.Commands
open Gallio.Common.Reflection 
open Gallio.Common.Diagnostics
open Gallio.Framework
open Gallio.Framework.Assertions
open System.Reflection
open System
                
///Manages test execution
[<AllowNullLiteral>]
type SpecController() =
    inherit TestController() with
        let nullTimeSpan = new System.Nullable<System.TimeSpan>()
        
        let rec skipAll (testCommand:ITestCommand) (parentTestStep:Tree.TestStep) : TestResult = 
            let context = testCommand.StartPrimaryChildStep parentTestStep
            for child in testCommand.Children do 
                skipAll child context.TestStep |> ignore
            context.FinishStep(TestOutcome.Skipped, nullTimeSpan)                    
        
        let createTestStep spec parentStep =
            let testStep = new Tree.TestStep(spec, parentStep, spec.Name, spec.CodeElement, false)
            testStep.IsDynamic <- true
            testStep.IsTestCase <- true
            testStep 


        let invoke (methodInfo:MethodInfo) =
            let executor = new SpecExecutor()
            executor.Run(methodInfo)

        let logResultAndFinishWith (result:SpecResult) (testContext:Contexts.ITestContext) : TestResult =
            let testTime = nullable result.Time 
            match result.Status with
            | TestStatus.Passed ->
                testContext.FinishStep (TestOutcome.Passed, testTime)
            | TestStatus.Failed ->
//                testContext.LogWriter.Failures.WriteException(
//                    new ExceptionData(result.ExceptionData), 
//                    "Exception"
//                )
                testContext.FinishStep (TestOutcome.Failed, testTime)
            | TestStatus.Inconclusive ->
                testContext.FinishStep (TestOutcome.Inconclusive, testTime)
            | _ as status ->
//                testContext.LogWriter.Warnings.Write 
//                    "The test was skipped."
                testContext.FinishStep (TestOutcome.Skipped, testTime)
    
        let runSpec (testCommand:ITestCommand) parentStep : TestResult =
            let spec = testCommand.Test :?> Specification
            let testStep = createTestStep spec parentStep
            let methodInfo = (spec.CodeElement :?> IMethodInfo).Resolve(false)
            let context = testCommand.StartStep(testStep)
                        
            context.LifecyclePhase <- LifecyclePhases.Execute
             
            let testOutcome = invoke methodInfo
            logResultAndFinishWith testOutcome context   

        let rec runSpecs (testCommand:ITestCommand) parentTestStep (progress:IProgressMonitor) : TestResult = 
            let runChildTests (testCommand:ITestCommand) parentStep progress : TestResult =
                let ctx = testCommand.StartPrimaryChildStep(parentStep)
                let passed = 
                    Async.Parallel 
                        [ 
                            for child in testCommand.Children -> 
                                async { 
                                    return runSpecs child ctx.TestStep progress 
                                } 
                        ] |> Async.RunSynchronously 
                        |> Seq.forall(fun result -> result.Outcome.Status = TestStatus.Passed )

                if passed then
                    ctx.FinishStep(TestOutcome.Passed, nullable (new TimeSpan()))
                else
                    ctx.FinishStep(TestOutcome.Failed, nullable (new TimeSpan()))


            let test = testCommand.Test
            progress.SetStatus(test.Name)
            let result =
                match test with
                | :? Specification -> 
                    runSpec testCommand parentTestStep
                | _ -> runChildTests testCommand parentTestStep progress
            progress.Worked(1.)
            result

        override this.RunImpl(rootTestCommand, parentTestStep, options, progressMonitor:IProgressMonitor) : TestResult= 
            
            use task = progressMonitor.BeginTask("Running Fspec tests", float rootTestCommand.TestCount)
            if options.SkipTestExecution then
                skipAll rootTestCommand parentTestStep
            else
                runSpecs rootTestCommand parentTestStep progressMonitor
                
